/* @flow */

import Dep from './dep'
import VNode from '../vdom/vnode'
import { arrayMethods } from './array'
import {
  def,
  warn,
  hasOwn,
  hasProto,
  isObject,
  isPlainObject,
  isPrimitive,
  isUndef,
  isValidArrayIndex,
  isServerRendering
} from '../util/index'

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

/**
 * In some cases we may want to disable observation inside a component's
 * update computation.
 */
export let shouldObserve: boolean = true

export function toggleObserving (value: boolean) {
  shouldObserve = value
}

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    // 每个对象(包括object，array)都一个自己的dep，主要用于：
    // 1、value为数组，调用push,pop等方法，需要触发更新；
    // 2、value为对象，进行$set,$delete操作的更新，例如obj = {value1: [1,2], value2: {a:1}}；
    this.dep = new Dep()
    this.vmCount = 0
    // value.__ob__赋值为Observer的实例
    def(value, '__ob__', this); // 给value添加一个属性__ob__，表示该对象对应的Observer实例。注意'__ob__'是不可以枚举的，否则会死循环
    // 1、如果是数组，重写数组方法(push,pop,shift,unshift,splice,sort,reverse)
    if (Array.isArray(value)) {
      if (hasProto) { // (__proto__ in {}) 支持原型链，则修改原型链即可
        // 1）将value的原型__proto__，指向arrayMethods，用于拦截数组方法（这7个方法会改变数组本身，所以需要拦截）
        protoAugment(value, arrayMethods)
      } else {  // 不支持原型链，则将arrayMethods上的7个方法，循环拷贝到value上即可
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 对数组内的每一项，循环进行观察
      this.observeArray(value)
    } else {
      // 2、如果是对象，则递归调用defineReactive将其变为响应式数据(重写setter、getter)
      this.walk(value)
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

// helpers

/**
 * Augment a target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment a target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 1.如果不是对象直接return
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 2.如果已经观测过则直接返回上次观测的实例
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (  // value是纯对象(或者数组)，并且是可扩展的isExtensible（不能是冻结对象）,并且不是Vue的实例对象(_isVue来标识)
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 3.如果可以观测（对象、或数组）就进行观测
    ob = new Observer(value)
  }
  // 4.如果是根数据  vmCount标注为1
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  // 1、对象configurable为false，直接返回（比如通过Object.freeze冻结的对象)
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // 2.获取getter和setter
  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  // val是对象（数组），则递归observe(val)，返回子对象的Observer实例:childOb
  let childOb = !shallow && observe(val); // observer方法返回当前对象对应的Observer实例，每个Observer实例上都有一个dep属性(针对对象或数组)
  // 3、重新定义getter、setter
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // 依赖收集,每一个dep收集对应的watcher
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      // 创建渲染watcher时，Dep.target赋值为该watcher。然后对变量取值生成vnode，取值时会触发get方法
      if (Dep.target) {
        // 当前dep中(subs: Array<Watcher>)收集watcher，当dep对应的数据变化时，watche就会执行。同时，watcher中存dep，主要用于计算属性watcher
        dep.depend();
        /**
         * obj = { arr: [1, 2, { a: 1 }]}
         * 1) 对于对象，通过循环对象的key，通过Object.defineProperty给每个key添加一个dep
         * 2）如果value是一个"数组"，observe(value)时，会给这个值添加单独的dep。
         *     a) 当我们对数组取值时，会触发"数组"的dep来收集当前watcher。
         *     b) 当调用数组方法(push、pop)时，会调用通知去更新，即this.__ob__.dep.notify()
         */
        // 如果子对象的Observer实例存在(即childOb存在)，则对“子对象“进行依赖收集，包括数组、普通对象
        // 数组：1、数组进行push、pop时，就可以调用数组的notify进行更新, arr.__ob__.dep.notify()，路径为:src\core\observer\array.js 50行
        // 对象：2、对象进行$set、$delete时，就可以调用对象的notify进行更新, obj.__ob__.dep.notify()，路径为:src\core\instance\state.js 386行

        // 即：watcher中添加子dep，同时子dep中添加watcher
        if (childOb) {  // 新设置的val是一个对象（数组），此时childOb存在。如：{ arr: [1,[2,3]], obj: {a: 1} };
          childOb.dep.depend(); // childOb这个observer实例（对象/数组dep），去收集当前的watcher 
          // 如果value是一个数组，则对数组中的每一项内容继续进行依赖收集(可能出现数组arr中嵌套数组的情况)
          if (Array.isArray(value)) {  // 递归收集数组中的依赖
            // 如果数组中还有数组，例如"[[20,21],1,2]", 当我们通过vm.arr[0].push(100)时，也能触发更新
            // 即：对数组内部的每一项，也进行依赖收集(数组里的某一项，也可能是对象或数组)
            dependArray(value)
          }
        }
      }
      return value
    },
    // 派发更新，
    // 循环dep中的watchers，执行watcher.update -> queneWatcher -> nextTick(flushSchedulerQueue -> 依次执行watcher.run方法)
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      // 每个key都有自己的dep
      dep.notify()
    }
  })
}

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 * $set内部调用的是下面的set方法，set内部：
 *   对于数组：调用的数组的'splice'方法来添加数据并返回。由于数组的splice方法在observer阶段已经被重写，因此会触发更新。
 *   对于对象：调用defineReactive(ob.value, key, val)将该key定义为响应式。然后调用target.__ob__.dep.notify通知页面更新。
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 1、target是数组，且key是一个有效的下标
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    // 在索引key位置，通过splice方法添加数据(数组的splice方法已经被重写了)
    target.splice(key, 1, val)
    return val
  }
  // 2、如果是对象：
  // 如果target中已经存在该key，直接赋值后返回
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  // 获取target的Observer实例对象：__ob__
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if (!ob) {  // 说明target不是一个响应式对象，直接设置值即可，页面也不会更新
    target[key] = val
    return val
  }
  // 2) target是一个对象，调用defineReactive，将新添加的key变为响应式对象
  defineReactive(ob.value, key, val)
  // ob.dep，即targe对象自己的那个dep实例，触发更新，重新渲染页面
  ob.dep.notify()
  return val
}

/**
 * Delete a property and trigger change if necessary.
 * $delelte调用的是内部的del方法，del内部：
 *   针对数组：调用的数组的splice方法。由于数组的splice方法在observer阶段已经被重写，因此会触发更新。
 *   针对对象：直接删除对象的key。然后调用target.__ob__.dep.notify通知页面更新。
 * 
 */
export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 1、target是数组，且key是一个有效的下标
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 在索引key位置，通过splice方法删除数据(数组的splice方法已经被重写了)
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  if (!hasOwn(target, key)) {
    return
  }
  // 如果是对象，直接删除target.key，并更新视图
  delete target[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}

/**
 * Collect dependencies on array elements when the array is touched, since
 * we cannot intercept array element access like property getters.
 */
function dependArray (value: Array<any>) { // value = [{name: 'xxx'}, [1,2]]；
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]; // 当前数组里的某一项
    // 在observe阶段，每一项都调用了observe()方法来观察，如果存在'__ob__'属性，则表示它是一个对象，我们需要对该对象进行依赖收集
    e && e.__ob__ && e.__ob__.dep.depend(); 
    if (Array.isArray(e)) { // e又是一个数组
      dependArray(e)
    }
  }
}
